//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include "cpipeline.h"
#include <buola/os/cprocess.h>
#include <buola/os/csignalblocker.h>
#include <signal.h>
#include <buola/tty/input.h>
#include "cinterpreter.h"
#include <buola/tty/output.h>

namespace buola { namespace bush {

std::map<CProcess*,CPipeline*> CPipeline::mProcessMap;    
    
void CPipeline::SetOutput(std::string *pOut)
{
    mCommands.back()->SetOutput(pOut);
}
    
void CPipeline::CalcFullCommand()
{
    mFullCommand.clear();
    for(int i=0;i<mCommands.size();i++)
    {
        if(mFullCommand.length()>200)
            break;
        if(i>0)
            mFullCommand+=" | ";
        mFullCommand+=mCommands[i]->mCommand;
        for(int j=0;j<mCommands[i]->mArgs.size();j++)
        {
            if(mFullCommand.length()>200)
                break;
            mFullCommand+=' ';
            mFullCommand+=mCommands[i]->mArgs[j];
        }
    }
    
    if(mFullCommand.length()>200)
    {
        mFullCommand.resize(196);
        mFullCommand+=" ...";
    }
}
    
int CPipeline::Launch(bool pSetTitle)
{
    bool lFG=mSeparators.size()<mCommands.size(); //background with final &

    for(int i=0;i<mCommands.size();++i)
    {
        mCommands[i]->ExpandArgs();
        
        //builtin commands have some restrictions:
        if(mCommands[i]->IsBuiltin())
        {
            if(i!=0)
            {
                msg_error() << "only the first command in a pipeline can be a builtin command\n";
                return -1;
            }
            else if(!lFG)
            {
                msg_error() << "builtin commands can't be run in the background\n";
                return -1;
            }
        }
    }
 
    for(int i=0;i<mCommands.size()-1;i++)
    {
        mCommands[i]->CreateProcess(mCommands[i+1]);
        
        mTitle+=mCommands[i]->mCommand;
        mTitle+=" | ";
    }
    mCommands.back()->CreateProcess(nullptr);
    mTitle+=mCommands.back()->mCommand;

    if(pSetTitle)
        tty::set_window_title(mTitle);
    
    mPGID=mCommands[0]->Launch(0,lFG);
    mStatus=CProcess::STATUS_RUNNING;

    if(lFG)
    {
        Wait();
    }
    
    if(mStatus!=CProcess::STATUS_TERMINATED)
    {
        InitJob();

        gInterpreter->SetCurrentJob(mJobID);
        gInterpreter->SetPendingJobReport();
        mReportPending=true;
        
        return -1;
    }

    if(mCommands.back()->Signalled())
    {
        int lSignal=mCommands.back()->GetExitSignal();
        if(lSignal!=SIGINT)
            msg_info() << tty::color(tty::COLOR_BRIGHTRED,strsignal(mCommands.back()->GetExitSignal())) << "\n";
        return -1;
    }

    return mCommands.back()->GetExitCode();
}

void CPipeline::Continue(bool pFG)
{
    if(!mPGID) return;

    if(pFG)
    {
        os::CSignalBlocker lBlocker(SIGTTOU,SIGTTIN,SIGTSTP,SIGCHLD);
        tcsetpgrp(0,mPGID);
    }
    
    if(mStatus!=CProcess::STATUS_RUNNING)
    {
        killpg(mPGID,SIGCONT);
        mStatus=CProcess::STATUS_RUNNING;
        gInterpreter->SetCurrentJob(mJobID);
    }
    
    if(pFG)
    {
        Wait();
    }
    
    if(mStatus==CProcess::STATUS_TERMINATED)
    {
        RemoveJob();
    }
    else
    {
        gInterpreter->SetPendingJobReport();
        mReportPending=true;
    }
}

void CPipeline::Wait()
{
    for(int i=0;i<mCommands.size();i++)
        mCommands[i]->Wait();
        
    {
        os::CSignalBlocker lBlocker(SIGTTOU,SIGTTIN,SIGTSTP,SIGCHLD);
        tcsetpgrp(0,getpgrp());
    }

    mStatus=CProcess::STATUS_TERMINATED;
        
    for(int i=0;i<mCommands.size();i++)
    {
        if(mCommands[i]->GetStatus()==CProcess::STATUS_STOPPED)
            mStatus=CProcess::STATUS_STOPPED;
    }
}

void CPipeline::Check()
{
    CProcess::EStatus lOld=mStatus;
    
    mStatus=CProcess::STATUS_TERMINATED;
    
    for(int i=0;i<mCommands.size();i++)
    {
        if(mCommands[i]->GetStatus()!=CProcess::STATUS_TERMINATED)
            mStatus=mCommands[i]->GetStatus();
    }
    
    if(mStatus==lOld) return;
    
    if(mStatus==CProcess::STATUS_TERMINATED)
    {
        PrintStatus(false,msg_info());
        RemoveJob();
    }
    else
    {
        gInterpreter->SetPendingJobReport();
        mReportPending=true;
    }
}

void CPipeline::PrintStatus(bool pOnlyPending,io::CTextWriter &pOS)
{
    if(pOnlyPending&&!mReportPending)
        return;
    
    switch(mStatus)
    {
    default:
    case CProcess::STATUS_RUNNING:
        pOS << tty::color(tty::COLOR_BRIGHTGREEN,"RUNNING  ");
        break;
    case CProcess::STATUS_STOPPED:
        pOS << tty::color(tty::COLOR_BRIGHTRED,"STOPPED  ");
        break;
    case CProcess::STATUS_TERMINATED:
        pOS << tty::color(tty::COLOR_BRIGHTCYAN,"FINISHED  ");
        break;
    }
    std::string lString='['+to_string(mJobID)+']';
    if(mJobID==gInterpreter->GetCurrentJob())
        pOS << tty::color(tty::COLOR_BRIGHTWHITE,lString);
    else
        pOS << lString;
        
    pOS << std::string(6-lString.size(),' ') << mFullCommand << "\n";
    
    mReportPending=false;
}

void CPipeline::InitJob()
{
    CalcFullCommand();
    mJobID=gInterpreter->AddJob(this);
    
    for(int i=0;i<mCommands.size();i++)
    {
        if(mCommands[i]->mProcess)
        {
            mProcessMap[mCommands[i]->mProcess.get()]=this;
        }
    }
}

void CPipeline::RemoveJob()
{
    gInterpreter->RemoveJob(mJobID);

    for(int i=0;i<mCommands.size();i++)
    {
        if(mCommands[i]->mProcess)
        {
            mProcessMap.erase(mCommands[i]->mProcess.get());
        }
    }
}

CPipeline *CPipeline::GetPipeline(CProcess *pProcess)
{
    auto p=mProcessMap.find(pProcess);
    if(p==mProcessMap.end()) return nullptr;
    return p->second;
}

/*namespace bush*/ } /*namespace buola*/ }
